#include "syncup-service.h"
#include <syncup.h>
#include <common.h>
#include <types.h>
#include <utils.h>
#include <fsnotify.h>
#include <syncup_db.h>

#include <fcntl.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <dirent.h>

#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-bindings.h>


void
daemonize()
{
    int i, fd0, fd1, fd2;
    pid_t pid;
    struct rlimit rl;

    umask(0);

    if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
        g_error("syncup: can't get file limit: %s", strerror(errno));
    }

    /*
     * Become a session leader to lose controlling TTY.
     */
    if ((pid = fork()) < 0) {
        g_error("syncup: can't fork: %s", strerror(errno));
    } else if (pid != 0) { /* parent */
        exit(0);
    }
    setsid();

    /*
     * Ensure future opens won't allocate controlling TTYs.
     */
    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGHUP, &sa, NULL) < 0) {
        g_error("syncup: can't ignore SIGHUP: %s", strerror(errno));
    }
    if ((pid = fork()) < 0) {
        g_error("syncup: can't fork: %s", strerror(errno));
    } else if (pid != 0) { /* parent */
        exit(0);
    }

    const char *home = g_get_home_dir();
    if (chdir(home) < 0) {
        g_error("syncup: can't change directory to %s: %s", home,
                strerror(errno));
    }

    if (rl.rlim_max == RLIM_INFINITY)
        rl.rlim_max = 1024;
    for (i = 0; i < rl.rlim_max; i++) {
        close(i);
    }

    fd0 = open("/dev/null", O_RDWR);

    /*
     * Initialize the log file.
     */
    GString *log_path = g_string_new(syncup->local_home);
    g_string_append_c(log_path, G_DIR_SEPARATOR);
    g_string_append(log_path, SYNCUP_LOG_NAME);
    syncup->log = fopen(log_path->str, "a");
    if (syncup->log == NULL) {
        g_error("can't open log '%s': %s", log_path->str, strerror(errno));
    }
    g_string_free(log_path, TRUE);

    fd1 = fileno(syncup->log);
    fd2 = dup(1);

    if (fd0 != 0 || fd1 != 1 || fd2 != 2) {
        g_error("syncup: unexpected file descriptors %d %d %d", fd0, fd1, fd2);
    }
}

struct dbus_service_struct {
    pthread_t tid;
    GMainLoop *loop;
};

static struct dbus_service_struct dbus_service = {0, NULL};

static void *
dbus_service_thread(struct dbus_service_struct *service)
{
    guint result;
    GError *err = NULL;
    DBusGConnection *conn;
    DBusGProxy *proxy;
    GObject *gobj;

    dbus_g_thread_init();

    service->loop = g_main_loop_new(NULL, FALSE);
    conn = dbus_g_bus_get(DBUS_BUS_SESSION, &err);
    proxy = dbus_g_proxy_new_for_name(conn, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
            DBUS_INTERFACE_DBUS);

    org_freedesktop_DBus_request_name (proxy, SYNCUP_SERVICE_DBUS_NAME,
            DBUS_NAME_FLAG_DO_NOT_QUEUE, &result, &err);

    gobj = g_object_new(SYNCUP_TYPE_SERVICE, NULL);
    dbus_g_connection_register_g_object(conn, SYNCUP_SERVICE_DBUS_PATH, gobj);

    g_main_loop_run(service->loop);
    return NULL;
}

typedef void *(*thread_func_t)(void *);

static void
init_dbus_service()
{
    if (pthread_create(&dbus_service.tid, NULL,
            (thread_func_t) dbus_service_thread, &dbus_service)) {
        g_critical("can't start dbus service thread");
        return;
    }
}

static void
end_dbus_service()
{
    g_main_loop_quit(dbus_service.loop);
    pthread_join(dbus_service.tid, NULL);
}

static void
sigterm_handler(int signum)
{
    async_queue_interrupt(&syncup->evt_queue);
    g_message("syncup->evt_queue interrupted");
}

static void
setup_signal_handler()
{
    struct sigaction sa;
    sa.sa_handler = sigterm_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGTERM, &sa, NULL) < 0) {
        g_error("can't set SIGTERM handler: %s", strerror(errno));
    }
}

void
syncup_service_platform_init()
{
    init_dbus_service();
    setup_signal_handler();
}

void
syncup_service_platform_end()
{
    end_dbus_service();
}

int
syncup_add_path_to_db_recursively(char *name, uint32_t parent_inode,
        GString *path)
{
    DIR *dir;
    struct dirent *dp;
    gsize end = path->len;

    struct syncup_db_entry ent;
    ent.name = name;
    ent.parent = (int) parent_inode;
    ent.timestamp = 0; // FIXME: use correct timestamp
    if (insert_new_tuple(&ent, 0, 1)) {
        return -1;
    }

    dir = opendir(path->str);
    if (dir == NULL) {
        g_critical("can't open directory '%s': %s", path->str,
                strerror(errno));
        return -1;
    }
    while ((dp = readdir(dir)) != NULL) {
        if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
            continue;
        }
        if (dp->d_type == DT_DIR) {
            path_append_component(path, dp->d_name);
            syncup_add_path_to_db_recursively(dp->d_name, ent.inode, path);
            g_string_truncate(path, end);
        } else if (dp->d_type == DT_REG) {
            path_append_component(path, dp->d_name);
            syncup_add_file_to_db(dp->d_name, ent.inode, path);
            g_string_truncate(path, end);
        }
    }
    closedir(dir);
    return 0;
}
